"""
folder_monitor.py
监控文件夹中的文件变化:
1. 新增文件 将会以绿色显示
2. 修改文件 将会以黄色显示, 只有文件内容发生变化才会显示, 文件修改时间变化不会显示
3. 删除文件 将会以红色显示

修改folder_to_monitor和monitor_interval变量来指定要监控的文件夹和监控间隔时间
@author: huhuhu
@date: 2024-11-28

可以结合windows11的terminal半透明和置顶实现类似桌面歌词的效果:
https://zhuanlan.zhihu.com/p/517644421
ctrl + "+" 可以放大字体
ctrl + "-" 可以缩小字体
"""

import os
import time
import hashlib
import pathlib

# 监控的文件夹路径
folder_to_monitor = r'.\folder'
# 监控间隔时间（秒）
monitor_interval = 1
# log文件路径, 默认放在家目录下.cache/folder_monitor.log
log_file = os.path.expanduser('~/.cache/folder_monitor.log')

# 获取文件的md5值
def get_file_md5(filepath):
    with open(filepath, 'rb') as f:
        md5sum = hashlib.md5(f.read()).hexdigest()
    return md5sum

# 初始化文件修改时间缓存
def init_cache():
    cache = {}
    # 递归找到所有文件
    for filepath in pathlib.Path(folder_to_monitor).rglob("*"):
        if os.path.isfile(filepath):
            # 获取文件的修改时间和文件md5值
            moditime = os.path.getmtime(filepath)
            md5sum = get_file_md5(filepath)
            cache[filepath] = (moditime, md5sum)
    return cache

def init_log_file():
    if log_file is not None:
        # 如果log文件不存在则创建, 并确保目录存在
        if not os.path.exists(os.path.dirname(log_file)):
            os.makedirs(os.path.dirname(log_file))
        if not os.path.exists(log_file):
            with open(log_file, 'w') as f:
                f.write('')
        else:
            # 检测log大小,超过16M则提示清除
            if os.path.getsize(log_file) > 16 * 1024 * 1024:
                print("Warning: log file \"{}\" size is too large ({:.2f}M>16M), please clear it".format(pathlib.Path(log_file).absolute(), os.path.getsize(log_file) / 1024 / 1024))


# 检查文件改动
def check_changes(cache):
    changed_files = []
    new_files = []
    deleted_files = list(cache.keys())
    current_time = time.time()
    for filepath in pathlib.Path(folder_to_monitor).rglob("*"):
        if os.path.isfile(filepath):
            # 检查新文件或修改的文件
            moditime = os.path.getmtime(filepath)
            md5sum = get_file_md5(filepath)
            if filepath in cache:
                if cache[filepath][1] != md5sum:
                    changed_files.append((filepath, cache[filepath][0], moditime))
                deleted_files.remove(filepath)
            else:
                new_files.append((filepath, moditime))
            # 更新缓存
            cache[filepath] = moditime, md5sum

    # 检查删除的文件
    for deleted_file in deleted_files:
        del cache[deleted_file]

    return changed_files, new_files, deleted_files

def prt_changed_files(changed_files):
    for file, last, current in changed_files:
        last = time.ctime(last)
        current = time.ctime(current)
        print("\033[1;33m", "modified: \033[0m", current, "\033[1;33m", file, "\033[0m", "last modified: ", last)

def prt_new_files(new_files):
    for file, current in new_files:
        current = time.ctime(current)
        print("\033[1;32m", "created : \033[0m", current, "\033[1;32m", file, "\033[0m")

def prt_deleted_files(deleted_files):
    current = time.time()
    current = time.ctime(current)
    for file in deleted_files:
        print("\033[1;31m", "deleted : \033[0m", current, "\033[1;33m", file, "\033[0m")

def write_log(log):
    if log_file is not None:
        # 增加时间和换行
        log = time.strftime("%Y-%m-%d %H:%M:%S") + " " + log + "\n"
        with open(log_file, "a") as f:
            f.write(log)

def create_log(changed_files, new_files, deleted_files):
    log = ""
    if changed_files:
        log += "modified: " + str(changed_files) + " "
    if new_files:
        log += "created: " + str(new_files) + " "
    if deleted_files:
        log += "deleted: " + str(deleted_files)
    return log

def main():
    init_log_file()
    cache = init_cache()
    print("has been initialized, start monitoring...")
    write_log("monitor started")
    while True:
        try:
            changed_files, new_files, deleted_files = check_changes(cache)
            if changed_files:
                prt_changed_files(changed_files)
            if new_files:
                prt_new_files(new_files)
            if deleted_files:
                prt_deleted_files(deleted_files)
            
            if changed_files or new_files or deleted_files:
                log = create_log(changed_files, new_files, deleted_files)
                write_log(log)
            time.sleep(monitor_interval)
        except KeyboardInterrupt:
            print("monitor stopped by user")
            write_log("monitor stopped by user")
            break

if __name__ == "__main__":
    main()
